#include "pch.hpp"
#include "SystemState.hpp"
#include "Widget.hpp"
#include "Window.hpp"

using namespace Engine;
using namespace Engine::Ui;




SystemState::SystemState(const Cursor& cursor, Widget::weak_ptr modalWidget, const int2& parentPos)
	: m_cursor(cursor, -parentPos)
	, m_parentPos(parentPos)
{
	setModalWidget(modalWidget);
}
///////////////////////////////////////////////////////////////////////////////////////////////

void SystemState::post_create(ptr _this)
{
	m_this = _this;
}
///////////////////////////////////////////////////////////////////////////////////////////////

SystemState::~SystemState()
{}
///////////////////////////////////////////////////////////////////////////////////////////////




Widget::ptr SystemState::modalWidget() const
{
	return m_modalWidgets.top().first.lock();
}
///////////////////////////////////////////////////////////////////////////////////////////////

void SystemState::setModalWidget(Widget::weak_ptr modalWidget)
{
	auto widget = modalWidget.lock();
	if(!widget)
		return;

	auto conn = widget->destroyed.connect(Widget::notify::slot_type(&SystemState::modalWidgetDestroyed, this, _1)
		.track(m_this)
		.track(modalWidget)
	);
	m_modalWidgets.push(std::make_pair(modalWidget, conn));


	// Modal widgets should stay on top of any other widgets
	widget->bringToTop();

	foreach(auto pair, m_attachedWidgets)
	{
		pair.second.disconnect();
	}
	m_attachedWidgets.clear();

	foreach(auto pair, m_attachedBorders)
	{
		pair.second.second.disconnect();
	}
	m_attachedBorders.clear();
}
///////////////////////////////////////////////////////////////////////////////////////////////




Widget::ptr SystemState::focusWidget() const
{
	return m_focusWidgets.size() ? m_focusWidgets.top().first.lock() : Widget::ptr();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void SystemState::setFocusWidget(Widget::weak_ptr focusWidget)
{
	if(focusWidget.expired())
		return;

	auto widget = focusWidget.lock();

	auto conn = widget->destroyed.connect(Widget::notify::slot_type(&SystemState::focusWidgetDestroyed, this, _1)
		.track(m_this)
		.track(focusWidget)
	);
	m_focusWidgets.push(std::make_pair(focusWidget, conn));
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void SystemState::unsetFocusWidget(Widget::weak_ptr focusWidget)
{
	if(focusWidget.expired())
		return;

	auto widget = focusWidget.lock();

	if(SystemState::focusWidget() != widget)
		throw programming_error("The currently active modal widget does not match the given one");

	m_focusWidgets.top().second.disconnect();
	m_focusWidgets.pop();
}
///////////////////////////////////////////////////////////////////////////////////////////////////




Cursor& SystemState::cursor()
{
	return m_cursor;
}
///////////////////////////////////////////////////////////////////////////////////////////////

const Cursor& SystemState::cursor() const
{
	return m_cursor;
}
///////////////////////////////////////////////////////////////////////////////////////////////

int2 SystemState::parentPos() const
{
	return m_parentPos;
}
///////////////////////////////////////////////////////////////////////////////////////////////




void SystemState::attach(Widget::weak_ptr widget)
{
	auto w = widget.lock();
	if(!w)
		return;

	auto conn = widget.lock()->destroyed.connect(Widget::notify::slot_type(&SystemState::widgetDestroyed, this, _1)
		.track(m_this)
		.track(widget)
	);
	m_attachedWidgets.insert(std::make_pair(widget, conn));
}
///////////////////////////////////////////////////////////////////////////////////////////////////

bool SystemState::isAttached(Widget::weak_ptr widget) const
{
	return m_attachedWidgets.find(widget) != m_attachedWidgets.end();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void SystemState::detach(Widget::weak_ptr that)
{
	auto i = m_attachedWidgets.find(that);
	if(i == m_attachedWidgets.end())
		return;

	i->second.disconnect();
	m_attachedWidgets.erase(i);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void SystemState::attachBorder(boost::weak_ptr<Widget> that, Border::Type border)
{
	auto widget = that.lock();
	if(!widget)
		return;

	auto conn = widget->destroyed.connect(Widget::notify::slot_type(&SystemState::windowDestroyed, this, _1)
		.track(m_this)
		.track(that)
	);
	m_attachedBorders.insert(std::make_pair(that, std::make_pair(border, conn)));
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void SystemState::detachBorder(boost::weak_ptr<Widget> that)
{
	auto i = m_attachedBorders.find(that);
	if(i == m_attachedBorders.end())
		return;

	i->second.second.disconnect();
	m_attachedBorders.erase(i);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

bool SystemState::isAttachedBorder(boost::weak_ptr<Widget> widget) const
{
	return m_attachedBorders.find(widget) != m_attachedBorders.end();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

bool SystemState::hasAttached() const
{
	return /*m_attachedBorders.size() ||*/ m_attachedWidgets.size();
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void SystemState::moveAttached() const
{
	foreach(auto widget, m_attachedWidgets)
	{
		if(!widget.first.expired())
			widget.first.lock()->move(m_cursor.delta());
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void SystemState::resizeAttached() const
{
	int2 cursorPos   = m_cursor.pos();
	int2 cursorDelta = m_cursor.delta();

	foreach(auto pair, m_attachedBorders)
	{
		auto widget = pair.first.lock();
		auto resizingBorder = pair.second.first;

		auto pos        = widget->screenPos();
		auto size       = widget->size();
		auto minSize    = widget->minimumSize();
		auto clientSize = widget->clientSize();

		switch(resizingBorder)
		{
		case Border::Top:
			{
				int2 delta = cursorPos - pos;
				if(size.height - cursorDelta.y >= minSize.height)
				{
					widget->move(int2(0, delta.y));
					widget->resize(size - intsize2(0, delta.y));
				}
			}
			break;
		case Border::Right:
			{
				int2 delta = cursorPos - pos;
				if(delta.x >= minSize.width) widget->resize(intsize2(delta.x, size.height));
				else                         widget->resize(intsize2(minSize.width, size.height));
			}
			break;
		case Border::Bottom:
			{
				int2 delta = cursorPos - pos;
				if(delta.y >= minSize.height) widget->resize(intsize2(size.width, delta.y));
				else                          widget->resize(intsize2(size.width, minSize.height));
			}
			break;
		case Border::Left:
			{
				int2 delta = cursorPos - pos;

				if(size.width - cursorDelta.x >= minSize.width)
				{
					widget->move(int2(delta.x, 0));
					widget->resize(size - intsize2(delta.x, 0));
				}
			}
			break;
		}
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void SystemState::widgetDestroyed(Widget::weak_ptr widget)
{
	detach(widget);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void SystemState::windowDestroyed(boost::weak_ptr<Widget> widget)
{
	widgetDestroyed(widget);
	detachBorder(widget);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void SystemState::modalWidgetDestroyed(Widget::weak_ptr widget)
{
	if(widget.expired())
		return;

	if(modalWidget() == widget.lock())
	{
		if(m_modalWidgets.size())
		{
			auto modal = m_modalWidgets.top();
			if(modal.first == widget)
				m_modalWidgets.pop();
		}
	}
	else
	{
		throw programming_error("Modal widgets other than the currently active one must not be destroyed");
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void SystemState::focusWidgetDestroyed(Widget::weak_ptr widget)
{
	if(widget.expired())
		return;

	if(focusWidget() == widget.lock())
	{
		unsetFocusWidget(widget.lock());
	}
	else
	{
		throw programming_error("Focus widgets other than the currently active one must not be destroyed");
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
